{% extends "console/layout.html" %}
{% block title %}Call Records · {{site_name|default('RustPBX')}}{% endblock %}
{% block content %}
<div class="p-6">
    <div class="mx-auto max-w-7xl space-y-6" x-data='callRecordsConsole({
            basePath: {{ (base_path | default("/console")) | tojson }},
            filter_options: {{ filter_options | default({}) | tojson }},
            list_url: {{ list_url | default("/console/call-records") | tojson }},
            page_size_options: {{ page_size_options | default([10, 25, 50]) | tojson }}
        })' x-init="init()">
        <header class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
            <div class="space-y-1">
                <p class="text-xs font-semibold uppercase tracking-wide text-sky-600">Voice analytics</p>
                <h1 class="text-2xl font-semibold text-slate-900">Call records</h1>
                <p class="text-sm text-slate-500">Filter, export, and drill into SIP & media diagnostics for every
                    session.</p>
            </div>
            <div class="flex flex-wrap items-center gap-3">
                <button type="button"
                    class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-600 transition hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-sky-400 focus:ring-offset-2"
                    @click="clearFilters">
                    <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M10 3v14m-7-7h14" />
                    </svg>
                    Reset filters
                </button>
                <button type="button"
                    class="inline-flex items-center gap-2 rounded-lg bg-sky-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-400 focus:ring-offset-2"
                    @click="exportCsv">
                    <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M4 12l6 6 6-6" />
                        <path stroke-linecap="round" stroke-linejoin="round" d="M10 18V4" />
                    </svg>
                    Export CSV
                </button>
            </div>
        </header>

        <section class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
            <div class="rounded-xl bg-white p-4 shadow-sm ring-1 ring-black/5">
                <div class="text-xs font-semibold uppercase tracking-wide text-slate-400">Total calls</div>
                <div class="mt-2 flex items-baseline gap-2">
                    <span class="text-2xl font-semibold text-slate-900" x-text="summary.total"></span>
                    <span class="text-xs text-slate-500">records</span>
                </div>
            </div>
            <div class="rounded-xl bg-white p-4 shadow-sm ring-1 ring-black/5">
                <div class="text-xs font-semibold uppercase tracking-wide text-slate-400">Completion</div>
                <div class="mt-2 space-y-1 text-xs text-slate-600">
                    <div class="flex items-center justify-between">
                        <span>Answered</span>
                        <span class="font-semibold text-emerald-600" x-text="summary.answered"></span>
                    </div>
                    <div class="flex items-center justify-between">
                        <span>Missed</span>
                        <span class="font-semibold text-amber-600" x-text="summary.missed"></span>
                    </div>
                    <div class="flex items-center justify-between">
                        <span>Failed</span>
                        <span class="font-semibold text-rose-600" x-text="summary.failed"></span>
                    </div>
                </div>
            </div>
            <div class="rounded-xl bg-white p-4 shadow-sm ring-1 ring-black/5">
                <div class="text-xs font-semibold uppercase tracking-wide text-slate-400">Utilisation</div>
                <div class="mt-2 space-y-1 text-xs text-slate-600">
                    <div class="flex items-center justify-between">
                        <span>Avg duration</span>
                        <span class="font-semibold text-slate-900"
                            x-text="formatAvgDuration(summary.avg_duration)"></span>
                    </div>
                    <div class="flex items-center justify-between">
                        <span>Total minutes</span>
                        <span class="font-semibold text-slate-900" x-text="formatNumber(summary.total_minutes)"></span>
                    </div>
                    <div class="flex items-center justify-between">
                        <span>Unique DIDs</span>
                        <span class="font-semibold text-slate-900" x-text="summary.unique_dids"></span>
                    </div>
                </div>
            </div>
            <div class="rounded-xl bg-white p-4 shadow-sm ring-1 ring-black/5">
                <div class="text-xs font-semibold uppercase tracking-wide text-slate-400">ASR coverage</div>
                <div class="mt-2 flex items-baseline gap-2">
                    <span class="text-2xl font-semibold text-sky-600" x-text="summary.transcribed"></span>
                    <span class="text-xs text-slate-500">transcribed</span>
                </div>
                <p class="mt-2 text-[11px] text-slate-400" x-show="summary.transcribed">Ready for semantic search</p>
            </div>
        </section>

        <section class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
            <div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
                <div>
                    <h2 class="text-base font-semibold text-slate-900">Filters</h2>
                    <p class="text-xs text-slate-500">Combine time range, direction, and transcript readiness to focus
                        investigations.</p>
                </div>
                <div class="flex flex-wrap items-center gap-3 text-xs">
                    <div class="relative text-sm">
                        <input type="search" x-model.debounce.300ms="search"
                            class="w-72 rounded-lg border border-slate-200 px-3 py-2 text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                            placeholder="Search number, agent, tag...">
                        <svg class="pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400"
                            viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6">
                            <path stroke-linecap="round" stroke-linejoin="round"
                                d="M12.5 12.5l4 4m-2.5-6a5.5 5.5 0 11-11 0 5.5 5.5 0 0111 0z" />
                        </svg>
                    </div>
                    <div>
                        <label class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">Status</label>
                        <select x-model="statusFilter"
                            class="mt-1 rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                            <template x-for="option in filterOptions.status" :key="option">
                                <option :value="option" x-text="statusLabel(option)"></option>
                            </template>
                        </select>
                    </div>
                    <div>
                        <label
                            class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">Direction</label>
                        <select x-model="directionFilter"
                            class="mt-1 rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                            <template x-for="option in filterOptions.direction" :key="option">
                                <option :value="option" x-text="directionLabel(option)"></option>
                            </template>
                        </select>
                    </div>
                    <div>
                        <label class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">From</label>
                        <input type="date" x-model="dateFrom"
                            class="mt-1 rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                    </div>
                    <div>
                        <label class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">To</label>
                        <input type="date" x-model="dateTo"
                            class="mt-1 rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                    </div>
                    <label
                        class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-3 py-2 text-sm font-semibold text-slate-600 transition hover:bg-slate-50">
                        <input type="checkbox" class="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500"
                            x-model="onlyTranscribed">
                        Transcribed only
                    </label>
                </div>
                <div class="mt-4 flex flex-wrap gap-5 text-xs text-slate-600">
                    <div class="space-y-1">
                        <label
                            class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">Departments</label>
                        <div class="flex flex-wrap gap-2">
                            <template x-if="!filterOptions.departments.length">
                                <span
                                    class="rounded-full border border-dashed border-slate-200 px-3 py-1 text-xs text-slate-400">No
                                    departments</span>
                            </template>
                            <template x-for="dept in filterOptions.departments" :key="dept.id">
                                <label
                                    class="inline-flex items-center gap-2 rounded-full border border-slate-200 px-3 py-1 text-xs font-semibold text-slate-600 transition hover:border-sky-300 hover:text-sky-700">
                                    <input type="checkbox"
                                        class="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500"
                                        :value="String(dept.id)" x-model="selectedDepartments">
                                    <span x-text="dept.name"></span>
                                </label>
                            </template>
                        </div>
                    </div>
                    <div class="space-y-1">
                        <label class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">SIP
                            Trunks</label>
                        <div class="flex flex-wrap gap-2">
                            <template x-if="!filterOptions.sip_trunks.length">
                                <span
                                    class="rounded-full border border-dashed border-slate-200 px-3 py-1 text-xs text-slate-400">No
                                    trunks</span>
                            </template>
                            <template x-for="trunk in filterOptions.sip_trunks" :key="trunk.id">
                                <label
                                    class="inline-flex items-center gap-2 rounded-full border border-slate-200 px-3 py-1 text-xs font-semibold text-slate-600 transition hover:border-sky-300 hover:text-sky-700">
                                    <input type="checkbox"
                                        class="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500"
                                        :value="String(trunk.id)" x-model="selectedSipTrunks">
                                    <span x-text="trunk.display_name || trunk.name"></span>
                                </label>
                            </template>
                        </div>
                    </div>
                    <div class="flex-1 space-y-1">
                        <label class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">Tags</label>
                        <div class="flex flex-wrap gap-2">
                            <template x-if="!filterOptions.tags.length">
                                <span
                                    class="rounded-full border border-dashed border-slate-200 px-3 py-1 text-xs text-slate-400">No
                                    tags</span>
                            </template>
                            <template x-for="tag in filterOptions.tags" :key="tag">
                                <label
                                    class="inline-flex items-center gap-2 rounded-full border border-slate-200 px-3 py-1 text-xs font-semibold text-slate-600 transition hover:border-sky-300 hover:text-sky-700">
                                    <input type="checkbox"
                                        class="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500"
                                        :value="tag" x-model="selectedTags">
                                    <span x-text="tag"></span>
                                </label>
                            </template>
                        </div>
                    </div>
                </div>
            </div>

            <template x-if="exportedAt">
                <div class="mt-4 rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-3 text-xs text-emerald-700">
                    CSV exported <span class="font-semibold" x-text="exportedAt"></span>
                </div>
            </template>

            <template x-if="error">
                <div class="mt-4 rounded-lg border border-rose-200 bg-rose-50 px-4 py-3 text-xs text-rose-700"
                    x-text="error"></div>
            </template>

            <div class="mt-4 flex items-center gap-2 text-xs text-slate-500" x-show="loading">
                <svg class="h-4 w-4 animate-spin text-slate-400" viewBox="0 0 24 24">
                    <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"
                        fill="none"></circle>
                    <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
                </svg>
                <span>Loading records…</span>
            </div>

            <template x-if="!filteredRecords.length">
                <div
                    class="mt-8 flex flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-slate-200 py-16 text-center text-sm text-slate-400">
                    <svg class="h-10 w-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M8 6h13M3 12h18M8 18h13" />
                    </svg>
                    <div>No records match the current filters.</div>
                </div>
            </template>

            <div class="mt-6 overflow-x-auto" x-show="filteredRecords.length">
                <table class="min-w-full divide-y divide-slate-200 text-sm">
                    <thead class="bg-slate-50 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">
                        <tr>
                            <th scope="col" class="whitespace-nowrap px-4 py-2">Call</th>
                            <th scope="col" class="whitespace-nowrap px-4 py-2">Started</th>
                            <th scope="col" class="whitespace-nowrap px-4 py-2">Direction</th>
                            <th scope="col" class="whitespace-nowrap px-4 py-2">Caller</th>
                            <th scope="col" class="whitespace-nowrap px-4 py-2">Callee</th>
                            <th scope="col" class="whitespace-nowrap px-4 py-2">Duration / MOS</th>
                            <th scope="col" class="whitespace-nowrap px-4 py-2">Status</th>
                            <th scope="col" class="whitespace-nowrap px-4 py-2">Tags</th>
                            <th scope="col" class="whitespace-nowrap px-4 py-2">Transcript</th>
                            <th scope="col" class="whitespace-nowrap px-4 py-2 text-right">Actions</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-slate-100">
                        <template x-for="record in paginatedRecords" :key="record.id">
                            <tr class="align-middle transition hover:bg-slate-50">
                                <td class="whitespace-nowrap px-4 py-2">
                                    <div class="font-semibold text-slate-900" x-text="record.display_id || record.id">
                                    </div>
                                    <div class="mt-0.5 flex items-center gap-2 text-[11px] text-slate-500">
                                        <span class="max-w-[180px] truncate font-mono" :title="record.call_id"
                                            x-text="record.call_id || '—'"></span>
                                        <template x-if="record.call_id">
                                            <div class="flex items-center gap-1">
                                                <button type="button"
                                                    class="inline-flex h-5 w-5 items-center justify-center rounded border border-slate-200 text-slate-400 transition hover:border-slate-300 hover:text-slate-600"
                                                    title="Copy call ID" aria-label="Copy call ID"
                                                    @click.stop="copyCallId(record.call_id)">
                                                    <svg class="h-3 w-3" viewBox="0 0 20 20" fill="none"
                                                        stroke="currentColor" stroke-width="1.6">
                                                        <path stroke-linecap="round" stroke-linejoin="round"
                                                            d="M6 6h8v8H6z" />
                                                        <path stroke-linecap="round" stroke-linejoin="round"
                                                            d="M4 4h8" />
                                                    </svg>
                                                </button>
                                                <span class="text-[10px] font-semibold text-emerald-600"
                                                    x-show="copiedCallId === record.call_id"
                                                    x-transition.opacity>Copied</span>
                                            </div>
                                        </template>
                                    </div>
                                </td>
                                <td class="px-4 py-2">
                                    <div class="text-sm text-slate-700" x-text="formatDateTime(record.started_at)">
                                    </div>
                                </td>
                                <td class="px-4 py-2">
                                    <div class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold"
                                        :class="directionClasses(record.direction)">
                                        <svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                            stroke-width="1.6">
                                            <path stroke-linecap="round" stroke-linejoin="round"
                                                d="M5 10h10M10 5l5 5-5 5" />
                                        </svg>
                                        <span x-text="directionLabel(record.direction)"></span>
                                    </div>
                                    <div class="mt-1 flex items-center gap-1 text-[11px] text-slate-500">
                                        <svg class="h-3 w-3" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                            stroke-width="1.6">
                                            <path stroke-linecap="round" stroke-linejoin="round" d="M4 4h12v12H4z" />
                                            <path stroke-linecap="round" stroke-linejoin="round" d="M8 4v12M12 8h4" />
                                        </svg>
                                        <span x-text="record.sip_gateway || '—'"></span>
                                    </div>
                                </td>
                                <td class="px-4 py-2">
                                    <div class="text-sm font-semibold text-slate-800"
                                        x-text="record.cnam || record.from || '—'"></div>
                                    <div class="text-xs text-slate-500">
                                        <span class="font-mono" x-text="record.from || '—'"></span>
                                    </div>
                                </td>
                                <td class="px-4 py-2">
                                    <div class="text-sm font-semibold text-slate-800"
                                        x-text="record.agent || record.to || '—'"></div>
                                    <div class="text-xs text-slate-500 flex flex-wrap gap-1">
                                        <span class="font-mono" x-text="record.to || '—'"></span>
                                        <span class="text-[11px] text-slate-400" x-show="record.queue"
                                            x-text="record.queue"></span>
                                    </div>
                                </td>
                                <td class="whitespace-nowrap px-4 py-2">
                                    <div class="text-sm font-semibold text-slate-800"
                                        x-text="formatDuration(record.duration_secs)"></div>
                                    <div class="text-xs font-semibold" :class="qualityTone(record.quality?.mos)"
                                        x-text="record.quality && record.quality.mos !== undefined && record.quality.mos !== null ? formatNumber(record.quality.mos) : '—'">
                                    </div>
                                </td>
                                <td class="px-4 py-2">
                                    <span
                                        class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold"
                                        :class="statusClasses(record.status)">
                                        <span class="h-1.5 w-1.5 rounded-full" :class="statusDot(record.status)"></span>
                                        <span x-text="statusLabel(record.status)"></span>
                                    </span>
                                </td>
                                <td class="px-4 py-2">
                                    <div class="flex flex-wrap gap-1 text-[11px]">
                                        <template x-for="tag in (record.tags || [])" :key="record.id + tag">
                                            <span
                                                class="inline-flex items-center gap-1 rounded-full bg-slate-100 px-2 py-0.5 text-slate-600">
                                                <span x-text="tag"></span>
                                            </span>
                                        </template>
                                        <template x-if="!(record.tags || []).length">
                                            <span class="rounded-full bg-slate-50 px-2 py-0.5 text-slate-400">No
                                                tags</span>
                                        </template>
                                    </div>
                                </td>
                                <td class="px-4 py-2">
                                    <div class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold"
                                        :class="record.has_transcript ? 'bg-emerald-50 text-emerald-600' : 'bg-slate-100 text-slate-500'">
                                        <svg class="h-3 w-3" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                            stroke-width="1.6">
                                            <path stroke-linecap="round" stroke-linejoin="round"
                                                d="M5 10h10M10 5l5 5-5 5" />
                                        </svg>
                                        <span x-text="record.has_transcript ? 'Ready' : 'Pending'"></span>
                                    </div>
                                </td>
                                <td class="whitespace-nowrap px-4 py-2 text-right text-xs font-semibold">
                                    <div class="flex items-center justify-end gap-2">
                                        <a :href="recordDetailUrl(record)" title="View detail"
                                            class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-slate-200 text-slate-500 transition hover:border-sky-300 hover:text-sky-700">
                                            <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                                stroke-width="1.6">
                                                <path stroke-linecap="round" stroke-linejoin="round" d="M7 5l6 5-6 5" />
                                            </svg>
                                        </a>
                                        <template x-if="record.recording && record.recording.url">
                                            <a :href="record.recording.url" target="_blank" rel="noopener"
                                                title="Download audio"
                                                class="inline-flex h-8 w-8 items-center justify-center rounded-full border border-slate-200 text-slate-500 transition hover:border-sky-300 hover:text-sky-700">
                                                <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none"
                                                    stroke="currentColor" stroke-width="1.6">
                                                    <path stroke-linecap="round" stroke-linejoin="round"
                                                        d="M4 12l6 6 6-6" />
                                                    <path stroke-linecap="round" stroke-linejoin="round" d="M10 18V4" />
                                                </svg>
                                            </a>
                                        </template>
                                    </div>
                                </td>
                            </tr>
                        </template>
                    </tbody>
                </table>
                <div
                    class="flex flex-col gap-3 border-t border-slate-100 px-4 py-4 text-xs text-slate-500 sm:flex-row sm:items-center sm:justify-between">
                    <div>
                        <span x-text="filteredRecords.length ? pageRangeStart : 0"></span>
                        –
                        <span x-text="pageRangeEnd"></span>
                        of
                        <span x-text="filteredRecords.length"></span>
                        records
                    </div>
                    <div class="flex flex-wrap items-center gap-3">
                        <label class="flex items-center gap-2">
                            <span class="text-[11px] uppercase tracking-wide text-slate-400">Per page</span>
                            <select x-model.number="pageSize"
                                class="rounded-lg border border-slate-200 px-2 py-1 text-xs text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                                <template x-for="option in pageSizeOptions" :key="option">
                                    <option :value="option" x-text="option"></option>
                                </template>
                            </select>
                        </label>
                        <div
                            class="inline-flex items-center gap-1 rounded-full border border-slate-200 bg-white p-1 text-xs font-semibold text-slate-600">
                            <button type="button" class="rounded-full px-2 py-1 transition disabled:opacity-40"
                                :disabled="currentPage === 1" @click="prevPage">
                                Prev
                            </button>
                            <span class="px-2" x-text="`Page ${currentPage} / ${totalPages}`"></span>
                            <button type="button" class="rounded-full px-2 py-1 transition disabled:opacity-40"
                                :disabled="currentPage === totalPages" @click="nextPage">
                                Next
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </div>
</div>

<script>
    document.addEventListener('alpine:init', () => {
        Alpine.data('callRecordsConsole', (payload) => ({
            basePath: '/console',
            listEndpoint: '/console/call-records',
            records: [],
            summary: {
                total: 0,
                answered: 0,
                missed: 0,
                failed: 0,
                transcribed: 0,
                avg_duration: 0,
                total_minutes: 0,
                unique_dids: 0,
            },
            filterOptions: {
                status: ['any'],
                direction: ['any'],
                departments: [],
                sip_trunks: [],
                tags: [],
            },
            search: '',
            statusFilter: 'any',
            directionFilter: 'any',
            dateFrom: '',
            dateTo: '',
            onlyTranscribed: false,
            selectedDepartments: [],
            selectedSipTrunks: [],
            selectedTags: [],
            exportedAt: null,
            loading: false,
            error: null,
            pageSizeOptions: [10, 25, 50],
            pageSize: 10,
            currentPage: 1,
            totalPages: 1,
            totalItems: 0,
            debounceTimer: null,
            copiedCallId: null,
            copyTimer: null,
            detailBase: '',
            _sync: {
                filters: false,
                pageSize: false,
            },
            _activeRequest: null,
            init() {
                const data = typeof payload === 'string' ? JSON.parse(payload || '{}') : (payload || {});

                if (typeof data.basePath === 'string' && data.basePath) {
                    this.basePath = data.basePath;
                }

                if (typeof data.list_url === 'string' && data.list_url) {
                    this.listEndpoint = data.list_url;
                } else {
                    const normalizedBase = this.basePath === '/'
                        ? ''
                        : (this.basePath.endsWith('/')
                            ? this.basePath.slice(0, -1)
                            : this.basePath);
                    this.listEndpoint = `${normalizedBase}/call-records` || '/call-records';
                }
                if (!this.listEndpoint.startsWith('/')) {
                    this.listEndpoint = `/${this.listEndpoint}`;
                }

                this.detailBase = this.normalizeBasePath(this.basePath);

                const options = (data.filter_options && typeof data.filter_options === 'object')
                    ? data.filter_options
                    : {};
                this.filterOptions = Object.assign({
                    status: ['any'],
                    direction: ['any'],
                    departments: [],
                    sip_trunks: [],
                    tags: [],
                }, options || {});
                this.filterOptions.status = Array.isArray(this.filterOptions.status)
                    ? this.filterOptions.status
                    : ['any'];
                this.filterOptions.direction = Array.isArray(this.filterOptions.direction)
                    ? this.filterOptions.direction
                    : ['any'];
                this.filterOptions.departments = Array.isArray(this.filterOptions.departments)
                    ? this.filterOptions.departments
                    : [];
                this.filterOptions.sip_trunks = Array.isArray(this.filterOptions.sip_trunks)
                    ? this.filterOptions.sip_trunks
                    : [];
                this.filterOptions.tags = Array.isArray(this.filterOptions.tags)
                    ? this.filterOptions.tags
                    : [];

                if (Array.isArray(data.page_size_options) && data.page_size_options.length) {
                    const normalized = data.page_size_options
                        .map((value) => Number(value))
                        .filter((value) => Number.isFinite(value) && value > 0);
                    if (normalized.length) {
                        this.pageSizeOptions = normalized;
                    }
                }

                const initialPerPage = Number(data.initial_per_page);
                if (Number.isFinite(initialPerPage) && initialPerPage > 0) {
                    this.pageSize = initialPerPage;
                } else if (this.pageSizeOptions.length) {
                    this.pageSize = this.pageSizeOptions[0];
                }

                if (!this.pageSizeOptions.includes(this.pageSize)) {
                    this.pageSizeOptions = [...this.pageSizeOptions, this.pageSize]
                        .filter((value, index, array) => array.indexOf(value) === index)
                        .sort((a, b) => a - b);
                }

                if (Array.isArray(data.records) && data.records.length) {
                    this.records = data.records;
                }

                this.summary = this.normalizeSummary(data.summary);

                const totalItemsRaw = Number(data.total_items);
                if (Number.isFinite(totalItemsRaw) && totalItemsRaw >= 0) {
                    this.totalItems = totalItemsRaw;
                }

                const totalPagesRaw = Number(data.total_pages);
                if (Number.isFinite(totalPagesRaw) && totalPagesRaw >= 1) {
                    this.totalPages = totalPagesRaw;
                }

                const currentPageRaw = Number(data.page);
                if (Number.isFinite(currentPageRaw) && currentPageRaw >= 1) {
                    this.currentPage = currentPageRaw;
                }

                const scheduleWatcher = () => {
                    if (this._sync.filters) return;
                    this.scheduleFetch();
                };
                const immediateWatcher = () => {
                    if (this._sync.filters) return;
                    this.applyFilters();
                };

                this.$watch('search', scheduleWatcher);
                this.$watch('statusFilter', immediateWatcher);
                this.$watch('directionFilter', immediateWatcher);
                this.$watch('dateFrom', immediateWatcher);
                this.$watch('dateTo', immediateWatcher);
                this.$watch('onlyTranscribed', immediateWatcher);
                this.$watch('selectedDepartments', immediateWatcher);
                this.$watch('selectedSipTrunks', immediateWatcher);
                this.$watch('selectedTags', immediateWatcher);
                this.$watch('pageSize', (value, oldValue) => {
                    if (this._sync.pageSize) {
                        return;
                    }
                    if (!Number.isFinite(value) || value <= 0 || value === oldValue) {
                        return;
                    }
                    this.applyPageSize(value);
                });

                if (!Array.isArray(data.records) || !data.records.length) {
                    this.fetchRecords({ page: this.currentPage });
                }
            },
            normalizeBasePath(value) {
                if (!value || value === '/') {
                    return '';
                }
                return value.endsWith('/') ? value.slice(0, -1) : value;
            },
            normalizeSummary(summary) {
                const defaults = {
                    total: 0,
                    answered: 0,
                    missed: 0,
                    failed: 0,
                    transcribed: 0,
                    avg_duration: 0,
                    total_minutes: 0,
                    unique_dids: 0,
                };
                if (!summary || typeof summary !== 'object') {
                    return defaults;
                }
                return Object.assign({}, defaults, summary);
            },
            scheduleFetch() {
                if (this.debounceTimer) {
                    clearTimeout(this.debounceTimer);
                }
                this.debounceTimer = setTimeout(() => {
                    this.debounceTimer = null;
                    this.currentPage = 1;
                    this.fetchRecords({ page: 1 });
                }, 300);
            },
            applyFilters() {
                this.currentPage = 1;
                this.fetchRecords({ page: 1 });
            },
            applyPageSize(size) {
                const perPage = Number(size);
                if (!Number.isFinite(perPage) || perPage <= 0) {
                    return;
                }
                this.currentPage = 1;
                this.fetchRecords({ page: 1, per_page: perPage });
            },
            buildRequestPayload(overrides = {}) {
                const filters = {};
                const query = (this.search || '').trim();
                if (query) {
                    filters.q = query;
                }

                const status = (this.statusFilter || '').toLowerCase();
                if (status && status !== 'any') {
                    filters.status = status;
                }

                const direction = (this.directionFilter || '').toLowerCase();
                if (direction && direction !== 'any') {
                    filters.direction = direction;
                }

                if (this.dateFrom) {
                    filters.date_from = this.dateFrom;
                }

                if (this.dateTo) {
                    filters.date_to = this.dateTo;
                }

                if (this.onlyTranscribed) {
                    filters.only_transcribed = true;
                }

                const deptIds = (Array.isArray(this.selectedDepartments) ? this.selectedDepartments : [])
                    .map((value) => parseInt(value, 10))
                    .filter((value) => Number.isInteger(value));
                if (deptIds.length) {
                    filters.department_ids = deptIds;
                }

                const sipIds = (Array.isArray(this.selectedSipTrunks) ? this.selectedSipTrunks : [])
                    .map((value) => parseInt(value, 10))
                    .filter((value) => Number.isInteger(value));
                if (sipIds.length) {
                    filters.sip_trunk_ids = sipIds;
                }

                const tags = (Array.isArray(this.selectedTags) ? this.selectedTags : [])
                    .map((value) => (value || '').toString().trim())
                    .filter((value) => value.length)
                    .filter((value, index, array) => array.indexOf(value) === index);
                if (tags.length) {
                    filters.tags = tags;
                }

                const page = Number.isFinite(overrides.page) && overrides.page ? overrides.page : this.currentPage;
                const perPage = Number.isFinite(overrides.per_page) && overrides.per_page > 0
                    ? overrides.per_page
                    : this.pageSize;

                const payload = {
                    page: Math.max(1, page),
                    per_page: Math.max(1, perPage),
                };

                if (Object.keys(filters).length) {
                    payload.filters = filters;
                }

                return payload;
            },
            async fetchRecords(options = {}) {
                const token = Symbol('call-records-request');
                this._activeRequest = token;
                this.loading = true;
                this.error = null;
                try {
                    const payload = this.buildRequestPayload(options);
                    const response = await fetch(this.listEndpoint, {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                        },
                        body: JSON.stringify(payload),
                    });
                    const data = await response.json();
                    if (!response.ok) {
                        throw new Error(data?.message || 'Failed to load call records');
                    }

                    if (this._activeRequest !== token) {
                        return;
                    }

                    const items = Array.isArray(data?.items) ? data.items : [];
                    this.records = items;

                    const perPageRaw = Number(data?.per_page);
                    const totalItemsRaw = Number(data?.total_items);
                    const totalPagesRaw = Number(data?.total_pages);
                    const currentPageRaw = Number(data?.page);

                    const perPage = Number.isFinite(perPageRaw) && perPageRaw > 0
                        ? perPageRaw
                        : payload.per_page;

                    if (this.pageSize !== perPage) {
                        this._sync.pageSize = true;
                        this.pageSize = perPage;
                        this.$nextTick(() => {
                            this._sync.pageSize = false;
                        });
                    }

                    const totalItems = Number.isFinite(totalItemsRaw) && totalItemsRaw >= 0
                        ? totalItemsRaw
                        : items.length;
                    const inferredTotalPages = Math.max(Math.ceil(totalItems / (perPage || 1)), 1);
                    const totalPages = Number.isFinite(totalPagesRaw) && totalPagesRaw >= 1
                        ? Math.max(totalPagesRaw, 1)
                        : inferredTotalPages;
                    const currentPage = Number.isFinite(currentPageRaw) && currentPageRaw >= 1
                        ? Math.min(currentPageRaw, totalPages)
                        : Math.min(payload.page, totalPages);

                    this.currentPage = currentPage;
                    this.totalItems = totalItems;
                    this.totalPages = totalPages;
                    this.summary = this.normalizeSummary(data?.summary);
                } catch (err) {
                    console.error(err);
                    if (this._activeRequest !== token) {
                        return;
                    }
                    this.error = err?.message || 'Unable to load call records';
                    this.records = [];
                    this.summary = this.normalizeSummary(null);
                    this.totalItems = 0;
                    this.totalPages = 1;
                    this.currentPage = 1;
                } finally {
                    if (this._activeRequest === token) {
                        this.loading = false;
                    }
                }
            },
            setPage(page) {
                const target = Math.min(
                    Math.max(Number(page) || 1, 1),
                    Math.max(this.totalPages || 1, 1),
                );
                if (target === this.currentPage) {
                    return;
                }
                this.currentPage = target;
                this.fetchRecords({ page: target });
            },
            prevPage() {
                if (this.currentPage > 1) {
                    this.setPage(this.currentPage - 1);
                }
            },
            nextPage() {
                if (this.currentPage < this.totalPages) {
                    this.setPage(this.currentPage + 1);
                }
            },
            get filteredRecords() {
                return Array.isArray(this.records) ? this.records : [];
            },
            get paginatedRecords() {
                return this.filteredRecords;
            },
            get pageRangeStart() {
                if (!this.filteredRecords.length) {
                    return 0;
                }
                return ((this.currentPage - 1) * this.pageSize) + 1;
            },
            get pageRangeEnd() {
                if (!this.filteredRecords.length) {
                    return 0;
                }
                return Math.min(this.pageRangeStart + this.filteredRecords.length - 1, this.totalItems);
            },
            clearFilters() {
                this._sync.filters = true;
                this.search = '';
                this.statusFilter = 'any';
                this.directionFilter = 'any';
                this.dateFrom = '';
                this.dateTo = '';
                this.onlyTranscribed = false;
                this.selectedDepartments = [];
                this.selectedSipTrunks = [];
                this.selectedTags = [];
                this.$nextTick(() => {
                    this._sync.filters = false;
                    this.applyFilters();
                });
            },
            async copyCallId(callId) {
                if (!callId) {
                    return;
                }
                const text = String(callId);
                let ok = await this.tryClipboardWrite(text);
                if (!ok) {
                    ok = this.copyCallIdFallback(text);
                }
                if (ok) {
                    this.showCopyConfirmation(text);
                }
            },
            async tryClipboardWrite(text) {
                if (typeof navigator === 'undefined' || !navigator.clipboard || !navigator.clipboard.writeText) {
                    return false;
                }
                try {
                    await navigator.clipboard.writeText(text);
                    return true;
                } catch (err) {
                    console.error('Clipboard API failed', err);
                    return false;
                }
            },
            showCopyConfirmation(value) {
                this.copiedCallId = value;
                if (this.copyTimer) {
                    clearTimeout(this.copyTimer);
                }
                this.copyTimer = setTimeout(() => {
                    this.copiedCallId = null;
                    this.copyTimer = null;
                }, 1500);
            },
            // Provide a best-effort fallback for legacy clipboard implementations.
            copyCallIdFallback(text) {
                try {
                    const temp = document.createElement('textarea');
                    temp.value = text;
                    temp.setAttribute('readonly', '');
                    temp.style.position = 'fixed';
                    temp.style.opacity = '0';
                    document.body.appendChild(temp);
                    temp.select();
                    temp.setSelectionRange(0, temp.value.length);
                    const success = document.execCommand('copy');
                    document.body.removeChild(temp);
                    return success;
                } catch (err) {
                    console.error('Copy fallback failed', err);
                    return false;
                }
            },
            recordDetailUrl(record) {
                if (record && record.detail_url) {
                    return record.detail_url;
                }
                const identifier = (record && (record.id || record.call_id)) || null;
                if (!identifier) {
                    return '#';
                }
                const base = this.detailBase ? this.detailBase : '';
                const path = `${base}/call-records/${encodeURIComponent(identifier)}`;
                return path.startsWith('/') ? path : `/${path}`;
            },
            async exportCsv() {
                try {
                    this.exportedAt = null;
                    const desired = Math.max(this.totalItems || 0, this.pageSize || 1);
                    const payload = this.buildRequestPayload({
                        page: 1,
                        per_page: Math.min(Math.max(desired, 1000), 10000),
                    });
                    const response = await fetch(this.listEndpoint, {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                        },
                        body: JSON.stringify(payload),
                    });
                    const data = await response.json();
                    if (!response.ok) {
                        throw new Error(data?.message || 'Failed to export CSV');
                    }
                    const rows = Array.isArray(data?.items) ? data.items : [];
                    if (!rows.length) {
                        this.exportedAt = 'No data to export';
                        return;
                    }
                    const csv = this.buildCsv(rows);
                    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
                    const url = URL.createObjectURL(blob);
                    const link = document.createElement('a');
                    link.href = url;
                    link.download = `call-records-${new Date().toISOString().slice(0, 10)}.csv`;
                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);
                    URL.revokeObjectURL(url);
                    this.exportedAt = new Date().toLocaleString();
                } catch (err) {
                    console.error(err);
                    this.exportedAt = err?.message || 'Export failed';
                }
            },
            buildCsv(records) {
                const header = [
                    'Call ID',
                    'Display ID',
                    'Started At',
                    'Direction',
                    'Status',
                    'From',
                    'To',
                    'Duration (s)',
                    'Agent',
                    'Queue',
                    'Tags',
                    'Transcript',
                ];
                return [header]
                    .concat(
                        records.map((record) => [
                            record?.id || '',
                            record?.display_id || '',
                            this.formatDateTime(record?.started_at),
                            this.directionLabel(record?.direction),
                            this.statusLabel(record?.status),
                            record?.from || '',
                            record?.to || '',
                            record?.duration_secs || 0,
                            record?.agent || '',
                            record?.queue || '',
                            (Array.isArray(record?.tags) ? record.tags : []).join('|'),
                            record?.has_transcript ? 'Yes' : 'No',
                        ])
                    )
                    .map((line) =>
                        line
                            .map((cell) => {
                                const value = cell === null || cell === undefined ? '' : String(cell);
                                const escaped = value.replace(/"/g, '""');
                                return `"${escaped}"`;
                            })
                            .join(',')
                    )
                    .join('\n');
            },
            statusLabel(value) {
                switch ((value || '').toLowerCase()) {
                    case 'completed':
                        return 'Completed';
                    case 'missed':
                        return 'Missed';
                    case 'failed':
                        return 'Failed';
                    case 'any':
                        return 'Any status';
                    default:
                        return value || 'Unknown';
                }
            },
            directionLabel(value) {
                switch ((value || '').toLowerCase()) {
                    case 'inbound':
                        return 'Inbound';
                    case 'outbound':
                        return 'Outbound';
                    case 'internal':
                        return 'Internal';
                    case 'any':
                        return 'Any direction';
                    default:
                        return value || 'Unknown';
                }
            },
            statusClasses(status) {
                switch ((status || '').toLowerCase()) {
                    case 'completed':
                        return 'bg-emerald-50 text-emerald-600 ring-1 ring-emerald-200';
                    case 'missed':
                        return 'bg-amber-50 text-amber-600 ring-1 ring-amber-200';
                    case 'failed':
                        return 'bg-rose-50 text-rose-600 ring-1 ring-rose-200';
                    default:
                        return 'bg-slate-100 text-slate-600 ring-1 ring-slate-200';
                }
            },
            statusDot(status) {
                switch ((status || '').toLowerCase()) {
                    case 'completed':
                        return 'bg-emerald-500';
                    case 'missed':
                        return 'bg-amber-500';
                    case 'failed':
                        return 'bg-rose-500';
                    default:
                        return 'bg-slate-400';
                }
            },
            directionClasses(direction) {
                switch ((direction || '').toLowerCase()) {
                    case 'inbound':
                        return 'border border-sky-100 bg-sky-50 text-sky-700';
                    case 'outbound':
                        return 'border border-emerald-100 bg-emerald-50 text-emerald-700';
                    case 'internal':
                        return 'border border-slate-200 bg-slate-50 text-slate-700';
                    default:
                        return 'border border-slate-200 bg-slate-50 text-slate-700';
                }
            },
            qualityTone(value) {
                const mos = Number(value || 0);
                if (mos >= 4.3) {
                    return 'text-emerald-600';
                }
                if (mos >= 3.8) {
                    return 'text-amber-600';
                }
                if (mos > 0) {
                    return 'text-rose-600';
                }
                return 'text-slate-500';
            },
            formatNumber(value) {
                if (value === null || value === undefined || Number.isNaN(Number(value))) {
                    return '—';
                }
                return Number(value).toLocaleString(undefined, { maximumFractionDigits: 1 });
            },
            formatAvgDuration(value) {
                if (!value) {
                    return '0s';
                }
                const seconds = Number(value);
                if (Number.isNaN(seconds)) {
                    return '—';
                }
                if (seconds >= 3600) {
                    const hours = Math.floor(seconds / 3600);
                    const minutes = Math.round((seconds % 3600) / 60);
                    return `${hours}h ${minutes}m`;
                }
                if (seconds >= 60) {
                    const minutes = Math.floor(seconds / 60);
                    const secs = Math.round(seconds % 60);
                    return `${minutes}m ${secs}s`;
                }
                return `${Math.round(seconds)}s`;
            },
            formatDuration(value) {
                const seconds = Number(value || 0);
                if (!seconds) {
                    return '00:00';
                }
                const hours = Math.floor(seconds / 3600);
                const minutes = Math.floor((seconds % 3600) / 60);
                const secs = Math.floor(seconds % 60);
                const pad = (num) => String(num).padStart(2, '0');
                if (hours > 0) {
                    return `${pad(hours)}:${pad(minutes)}:${pad(secs)}`;
                }
                return `${pad(minutes)}:${pad(secs)}`;
            },
            formatDateTime(value) {
                if (!value) {
                    return '—';
                }
                try {
                    const date = new Date(value);
                    if (Number.isNaN(date.getTime())) {
                        return value;
                    }
                    return date.toLocaleString();
                } catch (err) {
                    return value;
                }
            },
        }));
    });
</script>
{% endblock %}